"""
HC-SR04超声波测距传感器驱动
用于树莓派通过GPIO与HC-SR04超声波传感器通信
支持公制和英制单位测量
原作者: Al Audet
基于MIT许可证发布
"""

import time
import math
import RPi.GPIO as GPIO


class Measurement(object):
    """HC-SR04超声波测距传感器类

    默认使用公制单位。如需使用英制单位，请设置:
    unit='imperial'
    temperature=<华氏温度值>
    """

    def __init__(
        self,
        trig_pin,
        echo_pin,
        temperature=20,
        unit="metric",
        round_to=1,
        gpio_mode=GPIO.BCM,
    ):
        """初始化超声波传感器

        Args:
            trig_pin: 触发信号引脚号
            echo_pin: 回响信号引脚号
            temperature: 环境温度(默认20℃)
            unit: 单位制('metric'或'imperial')
            round_to: 结果保留小数位数
            gpio_mode: GPIO模式(GPIO.BCM或GPIO.BOARD)
        """
        self.trig_pin = trig_pin
        self.echo_pin = echo_pin
        self.temperature = temperature
        self.unit = unit
        self.round_to = round_to
        self.gpio_mode = gpio_mode

    def raw_distance(self, sample_size=11, sample_wait=0.1):
        """获取经过温度校正的原始距离值(单位:cm)

        返回的是sample_size个采样值的中位数

        Args:
            sample_size: 采样数量(默认11次)
            sample_wait: 采样间隔时间(默认0.1秒)

        Returns:
            校正后的距离值(cm)

        Note:
            采样速度受sample_size和sample_wait两个参数影响
            - 减小sample_size可提高速度但可能增加误差
            - 减小sample_wait也可提高速度但会增加CPU使用率
            - 默认值sample_wait=0.1是速度和CPU使用率的良好平衡
        """
        # 温度单位转换
        if self.unit == "imperial":
            self.temperature = (self.temperature - 32) * 0.5556
        elif self.unit == "metric":
            pass
        else:
            raise ValueError("单位类型错误。单位必须是imperial或metric")

        # 计算声速(考虑温度影响)
        speed_of_sound = 331.3 * math.sqrt(1 + (self.temperature / 273.15))
        sample = []

        # 设置GPIO
        GPIO.setwarnings(False)
        GPIO.setmode(self.gpio_mode)
        GPIO.setup(self.trig_pin, GPIO.OUT)
        GPIO.setup(self.echo_pin, GPIO.IN)

        # 进行多次采样
        for distance_reading in range(sample_size):
            sonar_signal_off = 0
            sonar_signal_on = 1000000

            # 发送触发信号
            GPIO.output(self.trig_pin, GPIO.LOW)
            time.sleep(sample_wait)
            GPIO.output(self.trig_pin, True)
            time.sleep(0.00001)
            GPIO.output(self.trig_pin, False)

            # 等待回响信号
            echo_status_counter = 1
            while GPIO.input(self.echo_pin) == 0:
                if echo_status_counter < 1000:
                    sonar_signal_off = time.time()
                    echo_status_counter += 1
                else:
                    raise SystemError("未收到回响信号")

            while GPIO.input(self.echo_pin) == 1:
                sonar_signal_on = time.time()

            # 计算距离
            time_passed = sonar_signal_on - sonar_signal_off
            distance_cm = time_passed * ((speed_of_sound * 100) / 2)
            sample.append(distance_cm)

        # 清理GPIO
        GPIO.cleanup((self.trig_pin, self.echo_pin))

        # 返回中位数
        sorted_sample = sorted(sample)
        return sorted_sample[sample_size // 2]

    def depth_metric(self, median_reading, hole_depth):
        """计算液体的公制深度

        Args:
            median_reading: 传感器读数
            hole_depth: 传感器到洞底的距离(cm)

        Returns:
            液体的深度(cm)
        """
        return round(hole_depth - median_reading, self.round_to)

    def depth_imperial(self, median_reading, hole_depth):
        """计算液体的英制深度

        Args:
            median_reading: 传感器读数
            hole_depth: 传感器到洞底的距离(英寸)

        Returns:
            液体的深度(英寸)
        """
        return round(hole_depth - (median_reading * 0.394), self.round_to)

    def distance_metric(self, median_reading):
        """计算公制距离

        Args:
            median_reading: 传感器读数

        Returns:
            到目标的距离(cm)
        """
        return round(median_reading, self.round_to)

    def distance_imperial(self, median_reading):
        """计算英制距离

        Args:
            median_reading: 传感器读数

        Returns:
            到目标的距离(英寸)
        """
        return round(median_reading * 0.394, self.round_to)


if __name__ == "__main__":
    # 测试代码
    GPIO_TRIG = 12  # 触发信号引脚
    GPIO_ECHO = 16  # 回响信号引脚

    sonar = Measurement(GPIO_TRIG, GPIO_ECHO)
    print("开始测量距离...")

    try:
        while True:
            # 获取距离值
            distance = sonar.distance_metric(sonar.raw_distance(3, 0.08))
            print(f"距离: {distance} cm")
            time.sleep(0.5)

    except KeyboardInterrupt:
        print("\n程序已停止")
